Spring 5.1.8 (but also probably earlier versions)

Short version: if you use BeanNameAutoProxyCreator with a custom TargetSourceCreator, you'll get proxies created for all of your beans, not just the ones listed in the beanNames property.

Longer version: BeanNameAutoProxyCreator allows you to specify the beanNames for which you want proxies to be created. It also allows you to specify one of more TargetSourceCreators. However, the TargetSourceCreator will be asked to create TargetSource for every bean in the context, not just the beans listed in beanNames.

To reproduce the problem, I created a simple context with 2 beans and a unit test (see below). The context contains a BeanNameAutoProxyCreator and specifies that only beanA should be proxied. Both beanA and beanB are declared as lazy-init, and I've specified a LazyInitTargetSourceCreator to be used (the lazy-init-ness isn't particularly important, it's just an off-the-shelf implementation of TargetSourceCreator that demonstrates the problem).

The unit test asserts that BeanA is a proxy and beanB is not, but then fails because both beans have been proxied.

This is at the very least unexpected and confusing (and should be documented), and is probably a bug. The TargetSourceCreator is being asked for a TargetSource for every bean in the context, without checking for which beans should be candidates for being proxied.

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="beanA" class="test.BeanNameAutoProxyBugTest.ClassA" lazy-init="true"/>
    <bean id="beanB" class="test.BeanNameAutoProxyBugTest.ClassB" lazy-init="true"/>

    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames" value="beanA"/>
        <property name="customTargetSourceCreators">
            <bean class="org.springframework.aop.framework.autoproxy.target.LazyInitTargetSourceCreator"/>
        </property>
    </bean>
</beans>
package test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.aop.SpringProxy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;

import javax.inject.Inject;

import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/context.xml")
@TestExecutionListeners(DependencyInjectionTestExecutionListener.class)
public class BeanNameAutoProxyBugTest {

    @Inject
    ClassA beanA;

    @Inject
    ClassB beanB;

    @Test
    public void test() {
        assertThat("beanA should be proxied", beanA, instanceOf(SpringProxy.class));
        assertThat("beanB should not be proxied", beanB, not(instanceOf(SpringProxy.class)));
    }

    static class ClassA {
    }

    static class ClassB {
    }
}

Comment From: sbrannen

Thanks for pointing this out.

In your particular example, the LazyInitTargetSourceCreator only returns a LazyInitTargetSource if the bean definition for a given bean is configured as lazy-init. Otherwise, LazyInitTargetSourceCreator would return null for the custom TargetSource and no proxy would be created based solely on the configuration of the custom TargetSourceCreator.

However, since beanB is configured with lazy-init="true", it also gets proxied even though it's not in the configured beanNames list.

That's the part that is counter-intuitive, and we'll address that in 5.3 by ensuring that a custom TargetSourceCreator is only applied to beans whose names match the configured beanNames list.

Comment From: kennymacleod

Hi @sbrannen. The use of LazyInitTargetSourceCreator was just a convenient example, the problem occurs regardless of what the TargetSourceCreator does. If any TargetSourceCreator is specified, then the beanNames collection will be ignored and every bean will be passed to the TargetSourceCreator. That's more than just counterintuitive, surely, that's a bug, essentially that BeanNameAutoProxyCreator simply does not work as designed when used with a TargetSourceCreator.

Comment From: sbrannen

The use of LazyInitTargetSourceCreator was just a convenient example, the problem occurs regardless of what the TargetSourceCreator does. If any TargetSourceCreator is specified, then the beanNames collection will be ignored and every bean will be passed to the TargetSourceCreator.

Indeed. I was merely pointing out that a TargetSourceCreator can actively decide if it should create a TargetSource. The LazyInitTargetSourceCreator does actively decide, and yet it still gets applied when you wouldn't expect it to in the example you provided. So I think we're in agreement on that point. 😉

That's more than just counterintuitive, surely, that's a bug, essentially that BeanNameAutoProxyCreator simply does not work as designed when used with a TargetSourceCreator.

I think one can certainly view that as a bug in the sense that it was probably unintentional. However, this behavior has been in place for a long time, and people may have come to rely on this "accidental feature". That's why we have currently slated the fix for 5.3 in order to avoid breaking code relying on this in previous releases.

Speaking of which, are you hoping to see this fix applied to 5.2.7 or any earlier branches?

Comment From: sbrannen

The proposed fix can be viewed here: https://github.com/sbrannen/spring-framework/commit/5eda4699f03ad4a8ee35e47ba0f7b39a3cacef79

Comment From: kennymacleod

Speaking of which, are you hoping to see this fix applied to 5.2.7 or any earlier branches?

That was my hope, but I appreciate that this is old code, old enough as you say that people my be relying on the "broken" behaviour, which makes it hard to back-port.

Comment From: sbrannen

Thanks for the feedback, @kennymacleod.

This has been addressed in 3c3e8e6a8bfd45e8f1c3f1df5c7ef66d85d5d5f1 for Spring Framework 5.3.