Skip to content

Commit 9614817

Browse files
committed
Do not proxy test instances based on "original instance" convention
Issue: SPR-17137
1 parent f6ee250 commit 9614817

File tree

10 files changed

+262
-200
lines changed

10 files changed

+262
-200
lines changed

spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -385,14 +385,17 @@ protected boolean isInfrastructureClass(Class<?> beanClass) {
385385
/**
386386
* Subclasses should override this method to return {@code true} if the
387387
* given bean should not be considered for auto-proxying by this post-processor.
388-
* <p>Sometimes we need to be able to avoid this happening if it will lead to
389-
* a circular reference. This implementation returns {@code false}.
388+
* <p>Sometimes we need to be able to avoid this happening, e.g. if it will lead to
389+
* a circular reference or if the existing target instance needs to be preserved.
390+
* This implementation returns {@code false} unless the bean name indicates an
391+
* "original instance" according to {@code AutowireCapableBeanFactory} conventions.
390392
* @param beanClass the class of the bean
391393
* @param beanName the name of the bean
392394
* @return whether to skip the given bean
395+
* @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#ORIGINAL_INSTANCE_SUFFIX
393396
*/
394397
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
395-
return false;
398+
return AutoProxyUtils.isOriginalInstance(beanName, beanClass);
396399
}
397400

398401
/**

spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractBeanFactoryAwareAdvisingPostProcessor.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -64,4 +64,10 @@ protected ProxyFactory prepareProxyFactory(Object bean, String beanName) {
6464
return proxyFactory;
6565
}
6666

67+
@Override
68+
protected boolean isEligible(Object bean, String beanName) {
69+
return (!AutoProxyUtils.isOriginalInstance(beanName, bean.getClass()) &&
70+
super.isEligible(bean, beanName));
71+
}
72+
6773
}

spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AutoProxyUtils.java

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -16,10 +16,12 @@
1616

1717
package org.springframework.aop.framework.autoproxy;
1818

19+
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
1920
import org.springframework.beans.factory.config.BeanDefinition;
2021
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2122
import org.springframework.core.Conventions;
2223
import org.springframework.lang.Nullable;
24+
import org.springframework.util.StringUtils;
2325

2426
/**
2527
* Utilities for auto-proxy aware components.
@@ -63,7 +65,9 @@ public abstract class AutoProxyUtils {
6365
* @param beanName the name of the bean
6466
* @return whether the given bean should be proxied with its target class
6567
*/
66-
public static boolean shouldProxyTargetClass(ConfigurableListableBeanFactory beanFactory, @Nullable String beanName) {
68+
public static boolean shouldProxyTargetClass(
69+
ConfigurableListableBeanFactory beanFactory, @Nullable String beanName) {
70+
6771
if (beanName != null && beanFactory.containsBeanDefinition(beanName)) {
6872
BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
6973
return Boolean.TRUE.equals(bd.getAttribute(PRESERVE_TARGET_CLASS_ATTRIBUTE));
@@ -81,7 +85,9 @@ public static boolean shouldProxyTargetClass(ConfigurableListableBeanFactory bea
8185
* @see org.springframework.beans.factory.BeanFactory#getType(String)
8286
*/
8387
@Nullable
84-
public static Class<?> determineTargetClass(ConfigurableListableBeanFactory beanFactory, @Nullable String beanName) {
88+
public static Class<?> determineTargetClass(
89+
ConfigurableListableBeanFactory beanFactory, @Nullable String beanName) {
90+
8591
if (beanName == null) {
8692
return null;
8793
}
@@ -102,12 +108,30 @@ public static Class<?> determineTargetClass(ConfigurableListableBeanFactory bean
102108
* @param targetClass the corresponding target class
103109
* @since 4.2.3
104110
*/
105-
static void exposeTargetClass(ConfigurableListableBeanFactory beanFactory, @Nullable String beanName,
106-
Class<?> targetClass) {
111+
static void exposeTargetClass(
112+
ConfigurableListableBeanFactory beanFactory, @Nullable String beanName, Class<?> targetClass) {
107113

108114
if (beanName != null && beanFactory.containsBeanDefinition(beanName)) {
109115
beanFactory.getMergedBeanDefinition(beanName).setAttribute(ORIGINAL_TARGET_CLASS_ATTRIBUTE, targetClass);
110116
}
111117
}
112118

119+
/**
120+
* Determine whether the given bean name indicates an "original instance"
121+
* according to {@link AutowireCapableBeanFactory#ORIGINAL_INSTANCE_SUFFIX},
122+
* skipping any proxy attempts for it.
123+
* @param beanName the name of the bean
124+
* @param beanClass the corresponding bean class
125+
* @since 5.1
126+
* @see AutowireCapableBeanFactory#ORIGINAL_INSTANCE_SUFFIX
127+
*/
128+
static boolean isOriginalInstance(String beanName, Class<?> beanClass) {
129+
if (!StringUtils.hasLength(beanName) || beanName.length() !=
130+
beanClass.getName().length() + AutowireCapableBeanFactory.ORIGINAL_INSTANCE_SUFFIX.length()) {
131+
return false;
132+
}
133+
return (beanName.startsWith(beanClass.getName()) &&
134+
beanName.endsWith(AutowireCapableBeanFactory.ORIGINAL_INSTANCE_SUFFIX));
135+
}
136+
113137
}

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,18 @@ public interface AutowireCapableBeanFactory extends BeanFactory {
107107
@Deprecated
108108
int AUTOWIRE_AUTODETECT = 4;
109109

110+
/**
111+
* Suffix for the "original instance" convention when initializing an existing
112+
* bean instance: to be appended to the fully-qualified bean class name,
113+
* e.g. "com.mypackage.MyClass.ORIGINAL", in order to enforce the given instance
114+
* to be returned, i.e. no proxies etc.
115+
* @since 5.1
116+
* @see #initializeBean(Object, String)
117+
* @see #applyBeanPostProcessorsBeforeInitialization(Object, String)
118+
* @see #applyBeanPostProcessorsAfterInitialization(Object, String)
119+
*/
120+
String ORIGINAL_INSTANCE_SUFFIX = ".ORIGINAL";
121+
110122

111123
//-------------------------------------------------------------------------
112124
// Typical methods for creating and populating external bean instances
@@ -264,9 +276,12 @@ void autowireBeanProperties(Object existingBean, int autowireMode, boolean depen
264276
* for callbacks but not checked against the registered bean definitions.
265277
* @param existingBean the existing bean instance
266278
* @param beanName the name of the bean, to be passed to it if necessary
267-
* (only passed to {@link BeanPostProcessor BeanPostProcessors})
279+
* (only passed to {@link BeanPostProcessor BeanPostProcessors};
280+
* can follow the {@link #ORIGINAL_INSTANCE_SUFFIX} convention in order to
281+
* enforce the given instance to be returned, i.e. no proxies etc)
268282
* @return the bean instance to use, either the original or a wrapped one
269283
* @throws BeansException if the initialization failed
284+
* @see #ORIGINAL_INSTANCE_SUFFIX
270285
*/
271286
Object initializeBean(Object existingBean, String beanName) throws BeansException;
272287

@@ -275,10 +290,13 @@ void autowireBeanProperties(Object existingBean, int autowireMode, boolean depen
275290
* instance, invoking their {@code postProcessBeforeInitialization} methods.
276291
* The returned bean instance may be a wrapper around the original.
277292
* @param existingBean the new bean instance
278-
* @param beanName the name of the bean
293+
* (only passed to {@link BeanPostProcessor BeanPostProcessors};
294+
* can follow the {@link #ORIGINAL_INSTANCE_SUFFIX} convention in order to
295+
* enforce the given instance to be returned, i.e. no proxies etc)
279296
* @return the bean instance to use, either the original or a wrapped one
280297
* @throws BeansException if any post-processing failed
281298
* @see BeanPostProcessor#postProcessBeforeInitialization
299+
* @see #ORIGINAL_INSTANCE_SUFFIX
282300
*/
283301
Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
284302
throws BeansException;
@@ -288,10 +306,13 @@ Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String b
288306
* instance, invoking their {@code postProcessAfterInitialization} methods.
289307
* The returned bean instance may be a wrapper around the original.
290308
* @param existingBean the new bean instance
291-
* @param beanName the name of the bean
309+
* (only passed to {@link BeanPostProcessor BeanPostProcessors};
310+
* can follow the {@link #ORIGINAL_INSTANCE_SUFFIX} convention in order to
311+
* enforce the given instance to be returned, i.e. no proxies etc)
292312
* @return the bean instance to use, either the original or a wrapped one
293313
* @throws BeansException if any post-processing failed
294314
* @see BeanPostProcessor#postProcessAfterInitialization
315+
* @see #ORIGINAL_INSTANCE_SUFFIX
295316
*/
296317
Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
297318
throws BeansException;

spring-test/src/main/java/org/springframework/test/context/support/DependencyInjectionTestExecutionListener.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -49,7 +49,7 @@ public class DependencyInjectionTestExecutionListener extends AbstractTestExecut
4949
* <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}.
5050
*/
5151
public static final String REINJECT_DEPENDENCIES_ATTRIBUTE = Conventions.getQualifiedAttributeName(
52-
DependencyInjectionTestExecutionListener.class, "reinjectDependencies");
52+
DependencyInjectionTestExecutionListener.class, "reinjectDependencies");
5353

5454
private static final Log logger = LogFactory.getLog(DependencyInjectionTestExecutionListener.class);
5555

@@ -76,7 +76,7 @@ public final int getOrder() {
7676
* from the test context, regardless of its value.
7777
*/
7878
@Override
79-
public void prepareTestInstance(final TestContext testContext) throws Exception {
79+
public void prepareTestInstance(TestContext testContext) throws Exception {
8080
if (logger.isDebugEnabled()) {
8181
logger.debug("Performing dependency injection for test context [" + testContext + "].");
8282
}
@@ -91,7 +91,7 @@ public void prepareTestInstance(final TestContext testContext) throws Exception
9191
* otherwise, this method will have no effect.
9292
*/
9393
@Override
94-
public void beforeTestMethod(final TestContext testContext) throws Exception {
94+
public void beforeTestMethod(TestContext testContext) throws Exception {
9595
if (Boolean.TRUE.equals(testContext.getAttribute(REINJECT_DEPENDENCIES_ATTRIBUTE))) {
9696
if (logger.isDebugEnabled()) {
9797
logger.debug("Reinjecting dependencies for test context [" + testContext + "].");
@@ -112,11 +112,12 @@ public void beforeTestMethod(final TestContext testContext) throws Exception {
112112
* @see #prepareTestInstance(TestContext)
113113
* @see #beforeTestMethod(TestContext)
114114
*/
115-
protected void injectDependencies(final TestContext testContext) throws Exception {
115+
protected void injectDependencies(TestContext testContext) throws Exception {
116116
Object bean = testContext.getTestInstance();
117+
Class<?> clazz = testContext.getTestClass();
117118
AutowireCapableBeanFactory beanFactory = testContext.getApplicationContext().getAutowireCapableBeanFactory();
118119
beanFactory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_NO, false);
119-
beanFactory.initializeBean(bean, testContext.getTestClass().getName());
120+
beanFactory.initializeBean(bean, clazz.getName() + AutowireCapableBeanFactory.ORIGINAL_INSTANCE_SUFFIX);
120121
testContext.removeAttribute(REINJECT_DEPENDENCIES_ATTRIBUTE);
121122
}
122123

spring-test/src/test/java/org/springframework/test/context/groovy/GroovySpringContextTests.java

Lines changed: 23 additions & 22 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-2018 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.
@@ -43,13 +43,6 @@
4343
@ContextConfiguration("context.groovy")
4444
public class GroovySpringContextTests implements BeanNameAware, InitializingBean {
4545

46-
private boolean beanInitialized = false;
47-
48-
private String beanName = "replace me with [" + getClass().getName() + "]";
49-
50-
@Autowired
51-
private ApplicationContext applicationContext;
52-
5346
private Employee employee;
5447

5548
@Autowired
@@ -63,60 +56,68 @@ public class GroovySpringContextTests implements BeanNameAware, InitializingBean
6356

6457
protected String bar;
6558

59+
@Autowired
60+
private ApplicationContext applicationContext;
61+
62+
private String beanName;
63+
64+
private boolean beanInitialized = false;
65+
6666

6767
@Autowired
68-
protected final void setEmployee(final Employee employee) {
68+
protected void setEmployee(Employee employee) {
6969
this.employee = employee;
7070
}
7171

7272
@Resource
73-
protected final void setBar(final String bar) {
73+
protected void setBar(String bar) {
7474
this.bar = bar;
7575
}
7676

7777
@Override
78-
public final void setBeanName(final String beanName) {
78+
public void setBeanName(String beanName) {
7979
this.beanName = beanName;
8080
}
8181

8282
@Override
83-
public final void afterPropertiesSet() throws Exception {
83+
public void afterPropertiesSet() {
8484
this.beanInitialized = true;
8585
}
8686

87+
8788
@Test
88-
public final void verifyBeanInitialized() {
89-
assertTrue("This test bean should have been initialized due to InitializingBean semantics.",
90-
this.beanInitialized);
89+
public void verifyBeanNameSet() {
90+
assertTrue("The bean name of this test instance should have been set to the fully qualified class name " +
91+
"due to BeanNameAware semantics.", this.beanName.startsWith(getClass().getName()));
9192
}
9293

9394
@Test
94-
public final void verifyBeanNameSet() {
95-
assertEquals("The bean name of this test instance should have been set to the fully qualified class name "
96-
+ "due to BeanNameAware semantics.", getClass().getName(), this.beanName);
95+
public void verifyBeanInitialized() {
96+
assertTrue("This test bean should have been initialized due to InitializingBean semantics.",
97+
this.beanInitialized);
9798
}
9899

99100
@Test
100-
public final void verifyAnnotationAutowiredFields() {
101+
public void verifyAnnotationAutowiredFields() {
101102
assertNull("The nonrequiredLong property should NOT have been autowired.", this.nonrequiredLong);
102103
assertNotNull("The application context should have been autowired.", this.applicationContext);
103104
assertNotNull("The pet field should have been autowired.", this.pet);
104105
assertEquals("Dogbert", this.pet.getName());
105106
}
106107

107108
@Test
108-
public final void verifyAnnotationAutowiredMethods() {
109+
public void verifyAnnotationAutowiredMethods() {
109110
assertNotNull("The employee setter method should have been autowired.", this.employee);
110111
assertEquals("Dilbert", this.employee.getName());
111112
}
112113

113114
@Test
114-
public final void verifyResourceAnnotationWiredFields() {
115+
public void verifyResourceAnnotationWiredFields() {
115116
assertEquals("The foo field should have been wired via @Resource.", "Foo", this.foo);
116117
}
117118

118119
@Test
119-
public final void verifyResourceAnnotationWiredMethods() {
120+
public void verifyResourceAnnotationWiredMethods() {
120121
assertEquals("The bar method should have been wired via @Resource.", "Bar", this.bar);
121122
}
122123

0 commit comments

Comments
 (0)