Skip to content

Commit 503d65d

Browse files
committed
Avoid JDK proxy against CGLIB Factory interface and assert required type when resolving dependency
Issue: SPR-14478 (cherry picked from commit 0e3f0bd)
1 parent fe17f8d commit 503d65d

File tree

5 files changed

+83
-34
lines changed

5 files changed

+83
-34
lines changed

spring-aop/src/main/java/org/springframework/aop/framework/ProxyProcessorSupport.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -139,7 +139,8 @@ protected boolean isConfigurationCallbackInterface(Class<?> ifc) {
139139
* @return whether the given interface is an internal language interface
140140
*/
141141
protected boolean isInternalLanguageInterface(Class<?> ifc) {
142-
return ifc.getName().equals("groovy.lang.GroovyObject");
142+
return (ifc.getName().equals("groovy.lang.GroovyObject") ||
143+
ifc.getName().endsWith(".cglib.proxy.Factory"));
143144
}
144145

145146
}

spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,8 @@ protected void inject(Object bean, String beanName, PropertyValues pvs) throws T
577577
String autowiredBeanName = autowiredBeanNames.iterator().next();
578578
if (beanFactory.containsBean(autowiredBeanName)) {
579579
if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
580-
this.cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName);
580+
this.cachedFieldValue = new ShortcutDependencyDescriptor(
581+
desc, autowiredBeanName, field.getType());
581582
}
582583
}
583584
}
@@ -661,8 +662,8 @@ protected void inject(Object bean, String beanName, PropertyValues pvs) throws T
661662
String autowiredBeanName = it.next();
662663
if (beanFactory.containsBean(autowiredBeanName)) {
663664
if (beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
664-
this.cachedMethodArguments[i] =
665-
new ShortcutDependencyDescriptor(descriptors[i], autowiredBeanName);
665+
this.cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
666+
descriptors[i], autowiredBeanName, paramTypes[i]);
666667
}
667668
}
668669
}
@@ -705,16 +706,19 @@ private Object[] resolveCachedArguments(String beanName) {
705706
@SuppressWarnings("serial")
706707
private static class ShortcutDependencyDescriptor extends DependencyDescriptor {
707708

708-
private final String shortcutBeanName;
709+
private final String shortcutName;
709710

710-
public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcutBeanName) {
711+
private final Class<?> requiredType;
712+
713+
public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcutName, Class<?> requiredType) {
711714
super(original);
712-
this.shortcutBeanName = shortcutBeanName;
715+
this.shortcutName = shortcutName;
716+
this.requiredType = requiredType;
713717
}
714718

715719
@Override
716720
public Object resolveShortcut(BeanFactory beanFactory) {
717-
return resolveCandidate(this.shortcutBeanName, beanFactory);
721+
return resolveCandidate(this.shortcutName, this.requiredType, beanFactory);
718722
}
719723
}
720724

spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -172,21 +172,6 @@ public Object resolveNotUnique(Class<?> type, Map<String, Object> matchingBeans)
172172
throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());
173173
}
174174

175-
/**
176-
* Resolve the specified bean name, as a candidate result of the matching
177-
* algorithm for this dependency, to a bean instance from the given factory.
178-
* <p>The default implementation calls {@link BeanFactory#getBean(String)}.
179-
* Subclasses may provide additional arguments or other customizations.
180-
* @param beanName the bean name, as a candidate result for this dependency
181-
* @param beanFactory the associated factory
182-
* @return the bean instance (never {@code null})
183-
* @since 4.3
184-
* @see BeanFactory#getBean(String)
185-
*/
186-
public Object resolveCandidate(String beanName, BeanFactory beanFactory) {
187-
return beanFactory.getBean(beanName);
188-
}
189-
190175
/**
191176
* Resolve a shortcut for this dependency against the given factory, for example
192177
* taking some pre-resolved information into account.
@@ -196,12 +181,32 @@ public Object resolveCandidate(String beanName, BeanFactory beanFactory) {
196181
* pre-cached information while still receiving {@link InjectionPoint} exposure etc.
197182
* @param beanFactory the associated factory
198183
* @return the shortcut result if any, or {@code null} if none
184+
* @throws BeansException if the shortcut could not be obtained
199185
* @since 4.3.1
200186
*/
201-
public Object resolveShortcut(BeanFactory beanFactory) {
187+
public Object resolveShortcut(BeanFactory beanFactory) throws BeansException {
202188
return null;
203189
}
204190

191+
/**
192+
* Resolve the specified bean name, as a candidate result of the matching
193+
* algorithm for this dependency, to a bean instance from the given factory.
194+
* <p>The default implementation calls {@link BeanFactory#getBean(String)}.
195+
* Subclasses may provide additional arguments or other customizations.
196+
* @param beanName the bean name, as a candidate result for this dependency
197+
* @param requiredType the expected type of the bean (as an assertion)
198+
* @param beanFactory the associated factory
199+
* @return the bean instance (never {@code null})
200+
* @throws BeansException if the bean could not be obtained
201+
* @since 4.3.2
202+
* @see BeanFactory#getBean(String)
203+
*/
204+
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
205+
throws BeansException {
206+
207+
return beanFactory.getBean(beanName, requiredType);
208+
}
209+
205210

206211
/**
207212
* Increase this descriptor's nesting level.

spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,22 +1210,22 @@ protected Map<String, Object> findAutowireCandidates(
12101210
}
12111211
for (String candidateName : candidateNames) {
12121212
if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, descriptor)) {
1213-
result.put(candidateName, descriptor.resolveCandidate(candidateName, this));
1213+
result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this));
12141214
}
12151215
}
12161216
if (result.isEmpty() && !indicatesMultipleBeans(requiredType)) {
12171217
// Consider fallback matches if the first pass failed to find anything...
12181218
DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
12191219
for (String candidateName : candidateNames) {
12201220
if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, fallbackDescriptor)) {
1221-
result.put(candidateName, descriptor.resolveCandidate(candidateName, this));
1221+
result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this));
12221222
}
12231223
}
12241224
if (result.isEmpty()) {
12251225
// Consider self references before as a final pass
12261226
for (String candidateName : candidateNames) {
12271227
if (isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, fallbackDescriptor)) {
1228-
result.put(candidateName, descriptor.resolveCandidate(candidateName, this));
1228+
result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this));
12291229
}
12301230
}
12311231
}
@@ -1479,9 +1479,9 @@ public boolean isRequired() {
14791479
return false;
14801480
}
14811481
@Override
1482-
public Object resolveCandidate(String beanName, BeanFactory beanFactory) {
1483-
return (!ObjectUtils.isEmpty(args) ? beanFactory.getBean(beanName, args) :
1484-
super.resolveCandidate(beanName, beanFactory));
1482+
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) {
1483+
return (!ObjectUtils.isEmpty(args) ? beanFactory.getBean(beanName, requiredType, args) :
1484+
super.resolveCandidate(beanName, requiredType, beanFactory));
14851485
}
14861486
};
14871487
descriptorToUse.increaseNestingLevel();
@@ -1526,8 +1526,8 @@ public Object getObject(final Object... args) throws BeansException {
15261526
else {
15271527
DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) {
15281528
@Override
1529-
public Object resolveCandidate(String beanName, BeanFactory beanFactory) {
1530-
return beanFactory.getBean(beanName, args);
1529+
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) {
1530+
return ((AbstractBeanFactory) beanFactory).getBean(beanName, requiredType, args);
15311531
}
15321532
};
15331533
return doResolveDependency(descriptorToUse, this.beanName, null, null);

spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import java.util.concurrent.Future;
2727

2828
import org.junit.Test;
29+
import org.mockito.Mockito;
2930

3031
import org.springframework.aop.Advisor;
3132
import org.springframework.aop.framework.Advised;
@@ -37,6 +38,7 @@
3738
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
3839
import org.springframework.context.annotation.Bean;
3940
import org.springframework.context.annotation.Configuration;
41+
import org.springframework.context.annotation.Lazy;
4042
import org.springframework.core.Ordered;
4143
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
4244
import org.springframework.util.ReflectionUtils;
@@ -67,6 +69,18 @@ public void proxyingOccurs() {
6769
asyncBean.work();
6870
}
6971

72+
@Test
73+
public void proxyingOccursWithMockitoStub() {
74+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
75+
ctx.register(AsyncConfigWithMockito.class, AsyncBeanUser.class);
76+
ctx.refresh();
77+
78+
AsyncBeanUser asyncBeanUser = ctx.getBean(AsyncBeanUser.class);
79+
AsyncBean asyncBean = asyncBeanUser.getAsyncBean();
80+
assertThat(AopUtils.isAopProxy(asyncBean), is(true));
81+
asyncBean.work();
82+
}
83+
7084
@Test
7185
public void withAsyncBeanWithExecutorQualifiedByName() throws ExecutionException, InterruptedException {
7286
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
@@ -200,6 +214,20 @@ public Thread getThreadOfExecution() {
200214
}
201215

202216

217+
static class AsyncBeanUser {
218+
219+
private final AsyncBean asyncBean;
220+
221+
public AsyncBeanUser(AsyncBean asyncBean) {
222+
this.asyncBean = asyncBean;
223+
}
224+
225+
public AsyncBean getAsyncBean() {
226+
return asyncBean;
227+
}
228+
}
229+
230+
203231
@EnableAsync(annotation = CustomAsync.class)
204232
static class CustomAsyncAnnotationConfig {
205233
}
@@ -252,6 +280,17 @@ public AsyncBean asyncBean() {
252280
}
253281

254282

283+
@Configuration
284+
@EnableAsync
285+
static class AsyncConfigWithMockito {
286+
287+
@Bean @Lazy
288+
public AsyncBean asyncBean() {
289+
return Mockito.mock(AsyncBean.class);
290+
}
291+
}
292+
293+
255294
@Configuration
256295
@EnableAsync
257296
static class CustomExecutorAsyncConfig implements AsyncConfigurer {

0 commit comments

Comments
 (0)